/*
 * Copyright Iain Sproat, 2008
 * 
 *
 */
using System;
using System.Collections.Generic;

namespace FreeBase.Fluent
{
	/// <summary>
	/// FBDirective is used to provide a directive
	/// to a query e.g. count the number of topics returned,
	/// or limit the number of topics returned
	/// </summary>
	public sealed class FBDirective
	{
		#region Private variables
		FBQuery _parentQuery = null;
		DirectiveType _dirType = DirectiveType.Undefined;
		string[] _directiveValues = null;
		#endregion
		
		#region .ctor
		internal FBDirective( DirectiveType directiveType, FBQuery returning )
		{
			if( returning == null )
				throw new ArgumentNullException( "returning" );

			_dirType = directiveType;
			_parentQuery = returning;
		}
		#endregion
		
		#region Fluent Methods and Properties
		/// <summary>
		/// This is to be used with the Limit property,
		/// </summary>
		/// <param name="numberOfEntriesToLimitTo">The maximum number of responses to return</param>
		/// <returns></returns>
		public FBQuery To( int numberOfEntriesToLimitTo )
		{
			if( this._dirType != DirectiveType.Limit )
				throw new ApplicationException( "The Directive must be a limit in order to use the To() method" );
			_directiveValues = new string[] {numberOfEntriesToLimitTo.ToString()};
			return _parentQuery;
		}
		
		/// <summary>
		/// This should only be used with the Sort property
		/// </summary>
		/// <param name="propertyName">The name of the property on which to sort</param>
		/// <returns></returns>
		public FBQuery By( params string[] propertyNames )
		{
			if( !(this._dirType == DirectiveType.Sort
			      || this._dirType == DirectiveType.ReverseSort) )
				throw new ApplicationException( "The Directive must be a sort in order to use the By() method" );
			
			if( propertyNames == null )
				throw new ArgumentNullException( "propertyNames" );
			if( propertyNames.Length < 1 )
				throw new ArgumentException( "propertyNames should have at least one Property Name to sort by" );
			
			_directiveValues = new string[propertyNames.Length];
			for(int i=0; i < propertyNames.Length; i++ )
			{
				if( string.IsNullOrEmpty( propertyNames[i] ) )
					throw new ArgumentException( "The property given at index " + i + " cannot be null or empty" );
				
				if( this._dirType == DirectiveType.ReverseSort )
				{
					_directiveValues[i] = "-" + propertyNames[i].ToLower();
				}
				else
					_directiveValues[i] = propertyNames[i].ToLower();
			}
			return this._parentQuery;
		}
		
		/// <summary>
		/// ByIndex should be used with the Sort command.
		/// It sorts topics by their default index if one exists
		/// </summary>
		public FBQuery ByIndex
		{
			get{
				return this.By( "index" );
			}
		}
		
		/// <summary>
		/// This should only be used with the Optional directive.
		/// </summary>
		public FBQuery Forbidden{
			get{
				this.SetOptionalValue( "forbidden" );
				return _parentQuery;
			}
		}
		
		public FBQuery True{
			get{
				this.SetOptionalValue( "true" );
				return _parentQuery;
			}
		}
		
		public FBQuery Count{
			get{
				if( this._dirType != DirectiveType.Return )
					throw new ApplicationException( "The Directive must be 'return' in order to use the Count property" );
				_directiveValues = new string[]{"count"};
				return _parentQuery;
			}
		}
		
		public FBQuery UnlessExists{
			get{
				SetCreateValue( "unless_exists" );
				return this._parentQuery;
			}
		}
		
		public FBQuery UnlessConnected{
			get{
				SetCreateValue( "unless_connected" );
				return this._parentQuery;
			}
		}
		
		public FBQuery Unconditional{
			get{
				SetCreateValue( "unconditional" );
				return this._parentQuery;
			}
		}
		
		public FBQuery Insert{
			get{
				SetConnectValue( "insert" );
				return this._parentQuery;
			}
		}
		
		public FBQuery Update{
			get{
				SetConnectValue( "update" );
				return this._parentQuery;
			}
		}
			
		public FBQuery Replace{
			get{
				SetConnectValue( "replace" );
				return this._parentQuery;
			}
		}
			
		public FBQuery Delete{
			get{
				SetConnectValue( "delete" );
				return this._parentQuery;
			}
		}
		
		#endregion
		
		#region Private Helper Functions
		private void SetOptionalValue( string optionalValue )
		{
			if( this._dirType != DirectiveType.Optional )
					throw new ApplicationException( "The Directive must be 'optional' in order to use the this property" );
				this._directiveValues = new string[]{ optionalValue };
		}
		
		private void SetCreateValue( string createValue )
		{
			if( this._dirType != DirectiveType.Create )
				throw new ApplicationException( "The Directive must be 'create'. Please call Create first" );
			if( string.IsNullOrEmpty( createValue ) )
				throw new ArgumentNullException( "createValue" );
			_directiveValues = new string[]{ createValue };
		}
		
		private void SetConnectValue( string connectValue )
		{
			if( this._dirType != DirectiveType.Connect )
				throw new ApplicationException( "The Directive must be 'connect'. Please call Connect first" );
			if( string.IsNullOrEmpty( connectValue ) )
				throw new ArgumentNullException( "createValue" );
			_directiveValues = new string[]{ connectValue };
		}
		#endregion
		
		#region Internal Properties for working with directives
		internal DirectiveType GetDirectiveType
		{
			get{ return this._dirType; }
		}
		internal string[] GetDirectiveValue
		{
			get{ return this._directiveValues; }
		}
		#endregion
	}
	
	public enum DirectiveType
	{
		Limit,
		Sort,
		ReverseSort,
		Optional,
		Count,
		Return,
		EstimateCount,
		Create,
		Connect,
		Undefined
	}
}
